import asyncio
import logging

from rs485tomqtt.device import Device
from ..mqtt_client import MqttClient

# 07规约读取
# | 数据标识（DI，高字节在后） | 含义 | 单位 |
# | -----------| --------------- | ----- |
# | `00000000` | 当前组合有功总电能 | kWh |
# | `00010000` | 当前正向有功总电能 | kWh |
# | `00020000` | 当前反向有功总电能 | kWh |
# | `00030000` | 当前组合无功总电能 | kvarh |
# | `00040000` | 当前正向无功总电能 | kvarh |
# | `00050000` | 当前反向无功总电能 | kvarh |
# | `02010100` | A 相电压         | V |
# | `02010200` | B 相电压         | V |
# | `02010300` | C 相电压         | V |
# | `02020100` | A 相电流         | A |
# | `02020200` | B 相电流         | A |
# | `02020300` | C 相电流         | A |
# | `02030000` | 三相电流和        | A |
# | `02030001` | 瞬时总有功功率     | kW |
# | `02030100` | A 相有功功率      | kW |
# | `02030200` | B 相有功功率      | kW |
# | `02030300` | C 相有功功率      | kW |
# | `02040001` | 瞬时总无功功率     | kvar |
# | `02040100` | A 相无功功率      | kvar |
# | `02040200` | B 相无功功率      | kvar |
# | `02040300` | C 相无功功率      | kvar |
# | `02050001` | 瞬时总视在功率     | kVA |
# | `02050100` | A 相视在功率      | kVA |
# | `02050200` | B 相视在功率      | kVA |
# | `02050300` | C 相视在功率      | kVA |
# | `02060001` | 总功率因数        | 1 |
# | `02060100` | A 相功率因数      | 1 |
# | `02060200` | B 相功率因数      | 1 |
# | `02060300` | C 相功率因数      | 1 |
# | `02070100` | 电网频率          | Hz |

_log = logging.getLogger("dlt645")


# 645协议 读取
class Meter645Client:
    def __init__(self, conf, mqttClient: MqttClient, deviceDic: dict[str, Device]):
        self.conf = conf
        self.mqttClient = mqttClient
        self.host = conf["host"]
        self.port = conf["port"]
        self.deviceDic = deviceDic
        # 连接状态
        self.is_connected = False
        self.reader = None
        self.writer = None

    # 构造报文
    def _make_frame(self, addr, di):
        addr = bytes.fromhex(addr)[::-1]  # 表地址，低位在前
        ctrl = b"\x11"  # 读数据
        di_bytes = bytes.fromhex(di)
        # 先逆序
        di_bytes = di_bytes[::-1]
        # 数据标识加33H
        di_encoded = bytes([(b + 0x33) & 0xFF for b in di_bytes])
        data_len = bytes([len(di_encoded)])

        frame_nocs = b"\x68" + addr + b"\x68" + ctrl + data_len + di_encoded
        cs = sum(frame_nocs) & 0xFF
        frame = frame_nocs + bytes([cs]) + b"\x16"
        # 在报文前面加上前导码 FE FE FE FE
        full_frame = b"\xFE\xFE\xFE\xFE" + frame
        return full_frame

    # 解析返回报文
    def parse_response(self, resp: bytes, decimal_places: int = 0):
        """
        解析 645-2007 返回报文
        - 去掉前导码 FE
        - 校验 CS
        - 解码数据域 (减0x33)
        - 返回 (控制码, 数据标识, 数据值bytes)
        """
        # 1. 去掉前导码 FE
        while resp and resp[0] == 0xFE:
            resp = resp[1:]

        if len(resp) < 12:
            raise Exception(f"报文过短: {resp.hex(' ')}")

        # 2. 校验和
        cs_calc = sum(resp[0:-2]) & 0xFF
        cs_recv = resp[-2]
        if cs_calc != cs_recv:
            raise Exception(
                f"校验错误: 计算值 {cs_calc:02X}, 接收值 {cs_recv:02X}, 报文: {resp.hex(' ')}"
            )

        # 3. 提取字段
        ctrl = resp[8]          # 控制码
        data_len = resp[9]      # 数据长度
        data_area = resp[10:10 + data_len]

        if len(data_area) != data_len:
            raise Exception(f"数据长度不符: 期望 {data_len}, 实际 {len(data_area)}")

        # 4. 数据域解码 (逐字节 -0x33)185 - 51
        decoded = bytes([(b - 0x33) & 0xFF for b in data_area])

        # 5. 前4字节是数据标识，后面是实际值
        di = decoded[0:4][::-1].hex().upper()  # 注意：DI是高字节在前
        value_bcd = decoded[4:][::-1]
        # 6. 将 BCD 转整数
        value_str = ""
        for b in value_bcd:
            high = (b >> 4) & 0x0F
            low = b & 0x0F
            value_str += f"{high}{low}"

        # 7. 插入小数点
        if decimal_places > 0:
            value = float(value_str) * decimal_places
        else:
            value = float(value_str)
        return ctrl, di, value

    # 读取数据
    async def read_a_phase_current(self, addr, setting: dict):
        di = setting["di"]
        frame = self._make_frame(addr, di)
        # _log.info(f"发送报文:{frame.hex(' ').upper()}")
        self.writer.write(frame)
        await self.writer.drain()

        # 读取响应 (至少 16字节，可能更长)
        resp = await asyncio.wait_for(self.reader.read(1024), timeout=10)
        if not resp or len(resp) < 12:
            raise Exception("无响应或报文过短")
        # _log.info(f"接收报文:{resp.hex(' ').upper()}")

        ctrl, di, value = self.parse_response(
            resp, decimal_places=setting["scale"])
        return value

    # 循环查询设备
    async def loop_src(self):
        for dev in self.deviceDic.values():
            self.deviceItemData = {}
            self.deviceItemData[dev.name] = {}
            topic = dev.topic
            # 查询指令
            for setting in dev.ts:
                try:
                    value = await self.read_a_phase_current(addr=dev.addr, setting=setting)
                except Exception as e:
                    _log.error(
                        f"查询报错{self.host}:{dev.name}:查询配置:{setting}:错误信息:{e}")
                self.deviceItemData[dev.name][setting["key"]] = value
            self.mqttClient.send_msg(
                topic + dev.name, self.deviceItemData[dev.name])

    # 检测连接状态
    async def check_connection(self) -> bool:
        try:
            # 尝试写空数据（或心跳帧）
            self.writer.write(b'')
            await self.writer.drain()
            return True
        except Exception:
            return False

    async def run_async_client(self):
        self.is_connected = await self.check_connection()
        if self.is_connected:
            await self.loop_src()
        else:
            try:
                self.reader, self.writer = await asyncio.wait_for(
                    asyncio.open_connection(self.host, self.port),
                    timeout=5
                )
                _log.info(f"串口服务器连接成功: {self.host}:{self.port}")
                await self.loop_src()
            except asyncio.TimeoutError:
                _log.error(f"串口服务器连接失败: {self.host}:{self.port}")
            except Exception as e:
                _log.error(f"{self.host}:{self.port}初始化连接异常:{e}")
